﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using log4net;

namespace MyLib.Data {
	/// <summary>
	/// 
	/// </summary>
	public class SecurityFileProvider : FileContextProvider {
		/// <summary>
		/// 
		/// </summary>
		private static ILog m_logger = LogManager.GetLogger(typeof(SecurityFileProvider));

		/// <summary>
		/// 
		/// </summary>
		private const string XsdFileName = @"Security.xsd";

		/*
		 * Constants of XML.
		 * 
		 */

		/// <summary>
		/// 
		/// </summary>
		private const string XmlResourcesName = "resources";

		/// <summary>
		/// 
		/// </summary>
		private const string XmlResourceName = "resource";

		/// <summary>
		/// 
		/// </summary>
		private const string XmlNameName = "name";

		/// <summary>
		/// 
		/// </summary>
		private const string XmlActionName = "action";

		/*
		 * Private members.
		 */

		private string m_securityFileName;

		private string m_cacheKey;

		/// <summary>
		/// 
		/// </summary>
		/// <param name="securityFileName"></param>
		/// <param name="cacheProvider"></param>
		public SecurityFileProvider(string securityFileName, ICacheProvider cacheProvider = null)
			: base(cacheProvider) {
			System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(securityFileName));
			securityFileName = securityFileName.Trim();

			m_securityFileName = securityFileName;
			m_cacheKey = GetCacheKeyByFileName(
				System.IO.Path.GetFileName(securityFileName));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public SecurityTable LoadDataTable() {
			SecurityTable dataTable = null;

			if (CacheProvider != null) {
				dataTable = CacheProvider[m_cacheKey] as SecurityTable;
			}

			if (dataTable == null) {
				dataTable = LoadDataTableFromFile(m_securityFileName);

				if (CacheProvider != null) {
					CacheProvider.Add(m_cacheKey, dataTable, m_securityFileName);
				}
			}

			return dataTable;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="securityFileName"></param>
		/// <returns></returns>
		private static SecurityTable LoadDataTableFromFile(string securityFileName) {
			m_logger.DebugFormat("Loading datatable from file {0}.", securityFileName);

			// Prepare schema.
			var schemas = new XmlSchemaSet();
			var xss = typeof(SecurityFileProvider)
				.Assembly
				.GetManifestResourceStream("MyLib." + XsdFileName); // Use internal schema.
			using (var xsr = XmlReader.Create(xss)) {
				schemas.Add(null, xsr);
			}

			var dt = new SecurityTable();
			if (!System.IO.File.Exists(securityFileName)) {
				return dt;
			}

			// Load code file.
			XElement root;
			using (var securityFileStream = new FileStream(
				securityFileName,
				FileMode.Open,
				FileAccess.Read)) {

				root = XmlExtensions.LoadXElement(securityFileStream, schemas);

				securityFileStream.Close();
			}

			foreach (var resElement in root.Elements(XmlResourceName)) {
				var name = resElement.Attribute(XmlNameName).Value;
				var actions = resElement.Attribute(XmlActionName).Value;

				var actionIdArray = actions.Split(',').Select(s => {
					int id;
					if (int.TryParse(s.Trim(), out id)) {
						return id;
					} else {
						return -1;
					}
				}).Where(id => id != -1).ToArray();

				foreach (var actionId in actionIdArray) {
					var row = dt.AddNewRow();

					row.BeginEdit();

					row.Name = name;
					row.ActionId = actionId;

					row.EndEdit();
				} // end of foreach.
			} // end of foreach.

			dt.AcceptChanges();

			return dt;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public IList<int> FindActionsByName(string name) {
			System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(name));

			name = name.Trim();

			var dt = LoadDataTable();
			return dt
				.Where(row => string.Equals(name, row.Name, StringComparison.OrdinalIgnoreCase))
				.Select(row => row.ActionId)
				.Distinct()
				.ToList();
		} // end of FindActionsByName.

		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <param name="actions"></param>
		/// <returns></returns>
		public bool IsMatched(string name, int[] actions) {
			System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(name));
			System.Diagnostics.Debug.Assert(actions != null);

			foreach (var action in FindActionsByName(name)) {
				if (!actions.Contains(action)) {
					return false;
				}
			} // end of foreach.

			return true;
		} // end of IsMatched.
	} // end of SecurityContext.
}
